//
// Created by longPc on 2024/5/4.
//

#include "OpenGLWidget14.h"

#include <qdebug.h>

namespace opengl14{
// 创建VBO和VAO对象
    unsigned int VBO, VAO, EBO;

/// 顶点着色器的位置数据
    float vertices[] = {
            0.3F, 0.3F, 0.0F, -1.0F, 0.0F, 0.0F, 1.0F, 1.0F, // top right
//            0.5F, -0.5F, 0.0F, // bottom right
//            -0.5F, 0.5F, 0.0F, // top left

            0.3F, -0.3F, 0.0F, 0.0F, 1.0F, 0.0F, 1.0F, 0.0F, // bottom right
            -0.3F, -0.3F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, // bottom left
            -0.3F, 0.3F, 0.0F, 0.5F, 0.5F, 0.5F, 0.0F, 1.0F// top left
    };

    /// EBO的数据 - 需要使用vertices的哪个点位
    unsigned int indices[] = {
            0, 1, 3,
            1, 2, 3
    };

}


OpenGLWidget14::OpenGLWidget14(QWidget *parent) {
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(on_timeout()));
    m_timer.start(100);
}

OpenGLWidget14::~OpenGLWidget14() {
//    glDeleteVertexArrays(2, opengl14::VAOs);
//    glDeleteBuffers(2, opengl14::VBOs);
//    glDeleteShader(opengl14::shaderProgram);
    // 使用当前状态 - 使用这个函数，我们能夠将当前OpenGL窗口环境设为当前线程的上下文
    makeCurrent();
    glDeleteVertexArrays(1, &opengl14::VAO);
    glDeleteBuffers(1, &opengl14::VBO);
    glDeleteBuffers(1, &opengl14::EBO);
//    glDeleteShader(opengl14::shaderProgram);
    // 完成当前状态
    doneCurrent();
}

void OpenGLWidget14::initializeGL() {
    qDebug() << "initializeGL";
    // 初始化opengl - 不初始化就使用不了opengl
    initializeOpenGLFunctions();

    // 加载顶点着色器
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders14/shapes.vert");
    // 加载片元着色器
    m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders14/shapes.frag");
    // 链接着色器
    bool isSuccess = m_shaderProgram.link();
    if (!isSuccess){
        qDebug() << "加载着色器失败：" << m_shaderProgram.log();
    }

    // VBO和VAO对象赋予ID
    glGenVertexArrays(1, &opengl14::VAO);
    glGenBuffers(1, &opengl14::VBO);

    // 绑定VBO和VAO对象
    glBindVertexArray(opengl14::VAO);
    glBindBuffer(GL_ARRAY_BUFFER, opengl14::VBO);

    // 为当前绑定到target的缓冲区对象创建一个新的数据存储
    // 如果data不是NULL，则使用来自此指针的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(opengl14::vertices), opengl14::vertices, GL_STATIC_DRAW);

    // 绑定位置
    // 先绑定，然后再取位置
    /// 告知显卡如何解析缓冲里的属性值 -> 用途是为顶点设定属性指针。
    //0：这是指的顶点属性的位置。在着色器中你需要用 layout(location = 0) 来设置它的位置。
    //3：指定顶点属性的大小。你的顶点属性是一个vec3，所以它包含3个值。因此这里的大小是3。
    //GL_FLOAT：指定数据的类型，这里是GL_FLOAT，也就是浮点型。
    //GL_FALSE：定义了我们是否希望数据被标准化。这里把它设置为GL_FALSE。
    //3 * sizeof(float)：这个是步长。由于每个顶点的属性连续排列，我们直接按照顶点属性的大小032B跨过它们就可以。
    //(void*) 0：这是位置数据在缓冲中起始的位置。因为我们从头开始，所以这里是0。
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),  (void*) 0);
    // 开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);

    // 绑定颜色
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 绑定纹理
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // 用完就清空下绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
//    glBindVertexArray(0);

    // 改变图形的渲染模式 - 用线条填充的方式
    // 第一个参数决定了函数操作的面。GL_FRONT_AND_BACK表示设置的模式将同时应用于物体的前向面和后向面。你也可以只改变前向面的模式（GL_FRONT）或后向面的模式（GL_BACK）。
    // 第二个参数决定了绘制模式。GL_LINE表示以线框模式绘制物体，也就是你只会看到物体的边缘线条，而不会看到物体表面的填充。
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // EBO的初始化 - 绑定小助理、然后带上数据 - 跟VAO差不多一样的初始化逻辑
    glGenBuffers(1, &opengl14::EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, opengl14::EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(opengl14::indices), opengl14::indices, GL_STATIC_DRAW);

    // 初始化纹理 - 变成图片
    // 开启透明度
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    p_texture0 = new QOpenGLTexture(QImage(":/images14/1.png").mirrored());
    p_texture1 = new QOpenGLTexture(QImage(":/images14/2.png").mirrored());
    p_texture2 = new QOpenGLTexture(QImage(":/images14/1.png"));

    // 解绑VAO、EBO
//    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // 设置个偏移量 - 记住一定要绑定！
    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue("ourOffiset", 0.4f);
    // 设置着色器中纹理的值
    m_shaderProgram.setUniformValue("texture0", 0);
    m_shaderProgram.setUniformValue("texture1", 1);
    m_shaderProgram.setUniformValue("texture2", 2);

}

void OpenGLWidget14::resizeGL(int w, int h) {
}

#include <QTime>
void OpenGLWidget14::paintGL() {
    qDebug() << "paintGL";
    glClearColor(0.2F, 0.3F, 0.3F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);

    // 矩阵旋转
    unsigned int time = QTime::currentTime().msec(); //获取毫秒数
    QMatrix4x4 matrix;
    // TODO 这两个顺序很有意思，先后执行效果不同，可以尝试下就知道区别了
    matrix.translate(0.5, -0.5, 0);
    matrix.rotate(time,0.0f, 0.0f, 1.0f);


//    glUseProgram(opengl14::shaderProgram);
    m_shaderProgram.bind();
    glBindVertexArray(opengl14::VAO);
//    glDrawArrays(GL_TRIANGLES, 0, 6); // 从0开始找6个点 - count代表几个点位 vertices几组数据
    switch (m_shape) {
        case Rect:{
            // 加载纹理
//            glActiveTexture(GL_TEXTURE0);
            p_texture0->bind(0);
//            glActiveTexture(GL_TEXTURE1);
            p_texture1->bind(1);
            p_texture2->bind(2);
            // 旋转
            m_shaderProgram.setUniformValue("theMatrix", matrix);
            // 使用EBO进行绘制
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
        }
        default:{}
    }
}

void OpenGLWidget14::drawShape(OpenGLWidget14::Shape shape) {
    m_shape = shape;
    update();
}

void OpenGLWidget14::wrieframeMode(bool mode) {
    makeCurrent();
    if (mode){
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }else{
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
    update();
    doneCurrent();
}

void OpenGLWidget14::on_timeout() {
    makeCurrent();
    update();
    doneCurrent();
}
